package 多线程;

import 多线程.Test.MyThread1;
import 多线程.Test.MyThread2;

//import java.util.Arrays;
//
//使用ReadWriteLock
//
//前面讲到的ReentrantLock保证了只有一个线程可以执行临界区(Critical)代码：
//
//public class Counter {
//	//new ReentrantLock()默认为非公平锁
//	//new ReentrantLock(true)为表示公平锁，即先请求，先获得。
//	//new ReentrantLock(false)为表示非公平锁
//    private final Lock lock = new ReentrantLock();
//    private int[] counts = new int[10];
//
//    public void inc(int index) {
//        lock.lock();
//        try {
//            counts[index] += 1;
//        } finally {
//            lock.unlock();
//        }
//    }
//
//    public int[] get() {
//        lock.lock();
//        try {
//            return Arrays.copyOf(counts, counts.length);
//        } finally {
//            lock.unlock();
//        }
//    }
//}
//但是有些时候，这种保护有点过头。因为我们发现，任何时刻，只允许一个线程修改，也就是调用inc()方法是必须获取锁，但是，get()方法
//只读取数据，不修改数据，它实际上允许多个线程同时调用。
//
//实际上我们想要的是：允许多个线程同时读，但只要有一个线程在写，其他线程就必须等待：
//
//     读      写
//读	允许	    不允许
//写	不允许	不允许
//使用ReadWriteLock可以解决这个问题，它保证：
//
//只允许一个线程写入（其他线程既不能写入也不能读取）；
//没有写入时，多个线程允许同时读（提高性能）。
//用ReadWriteLock实现这个功能十分容易。我们需要创建一个ReadWriteLock实例，然后分别获取读锁和写锁：
//
//public class Counter {
//    private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
//    private final Lock rlock = rwlock.readLock();
//    private final Lock wlock = rwlock.writeLock();
//    private int[] counts = new int[10];
//
//    public void inc(int index) {
//        wlock.lock(); // 加写锁
//        try {
//            counts[index] += 1;
//        } finally {
//            wlock.unlock(); // 释放写锁
//        }
//    }
//
//    public int[] get() {
//        rlock.lock(); // 加读锁
//        try {
//            return Arrays.copyOf(counts, counts.length);
//        } finally {
//            rlock.unlock(); // 释放读锁
//        }
//    }
//}
//把读写操作分别用读锁和写锁来加锁，在读取时，多个线程可以同时获得读锁，这样就大大提高了并发读的执行效率。
//使用ReadWriteLock时，适用条件是同一个数据，有大量线程读取，但仅有少数线程修改。
//例如，一个论坛的帖子，回复可以看做写入操作，它是不频繁的，但是，浏览可以看做读取操作，是非常频繁的，这种情况就可以使用ReadWriteLock。

//ReadWriteLock接口简介
//public interface ReadWriteLock {
//    /**
//     * Returns the lock used for reading.
//     *
//     * @return the lock used for reading
//     */
//    Lock readLock();
//
//    /**
//     * Returns the lock used for writing.
//     *
//     * @return the lock used for writing
//     */
//    Lock writeLock();
//}
//从源码中可知，ReadWriteLock里面只定义了两个方法：一个用来获取读锁，一个用来获取写锁。也就是说将文件的读写操作分开，分成2个锁来分配给线
//程，从而使得多个线程可以同时进行读操作，ReentrantReadWriteLock实现了ReadWriteLock接口。

//ReentrantLock（排他锁）具有完全互斥排他的效果，即同一时刻只允许一个线程访问，这样做虽然虽然保证了实例变量的线程安全性，但效率非常
//低下。ReadWriteLock接口的实现类ReentrantReadWriteLock读写锁就是为了解决这个问题。ReentrantReadWriteLock里面提供了很多
//丰富的方法，不过最主要的有两个方法：readLock()和writeLock()用来获取读锁和写锁。
//
//读写锁维护了两个锁，一个是读操作相关的锁也成为共享锁，一个是写操作相关的锁,也称为排他锁。通过分离读锁和写锁，其并发性比一般排他锁有了很大提升。
//
//多个读锁之间不互斥，读锁与写锁互斥，写锁与写锁互斥（只要出现写操作的过程就是互斥的）。在没有线程Thread进行写入操作时，进行读取操作的
//多个Thread都可以获取读锁，而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作，但是同一时刻
//只允许一个Thread进行写入操作。
//
//1）假如有多个线程要同时进行读操作的话，先看一下synchronized达到的效果：
//public class ReentrantReadWriteLockTest2 {
/*public class 使用ReadWriteLock {
    public static void main(String[] args) {
        final 使用ReadWriteLock test = new 使用ReadWriteLock();
        
        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

    }

    synchronized public void get(Thread thread) {
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName() + "正在进行读操作");
        }
        System.out.println(thread.getName() + "读操作完毕");
    }
}*/

//2）实现读写锁
/*import java.util.concurrent.locks.ReentrantReadWriteLock;
public class 使用ReadWriteLock {

    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final 使用ReadWriteLock test = new 使用ReadWriteLock();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

    }

    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在进行读操作");
            }
            System.out.println(thread.getName() + "读操作完毕");
        } finally {
            rwl.readLock().unlock();
        }
    }
}
*/
//thread1和thread2在同时进行读操作，这样就大大提升了读操作的效率。不过要注意的是，如果有一个线程已经占用了读锁，则此时其他线程如
//果要申请写锁，则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁，则此时其他线程如果申请写锁或者读锁，则申请的线程会一
//直等待释放写锁。

//3）读读共享
//public class ReentrantReadWriteLockTest {
/*import java.util.concurrent.locks.ReentrantReadWriteLock;
public class 使用ReadWriteLock {
    /**
     * ReentrantReadWriteLock的特性
     * 1.公平性选择:支持非公平（默认）和公平的锁获取方式，吞吐量上来看还是非公平优于公平
     * 2.重进入:该锁支持重进入，以读写线程为例：读线程在获取了读锁之后，能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁也
     * 能够同时获取读锁
     * 3.锁降级:遵循获取写锁、获取读锁再释放写锁的次序，写锁能够降级称为读锁
     */
/*    public static void main(String[] args) {
        Service service = new Service();
		//读读共享   两个线程可以同时或者说是几乎同时运行lock()方法后面的代码，输出的两句话显示的时间一样，这样提高了程序的运行效率。
		//MyThread1 t1=new MyThread1(service);//读
		//MyThread1 t2=new MyThread1(service);//读
		
		//写写互斥
		//同一时间只允许一个线程执行lock()方法后面的代码。
		//MyThread2 t1=new MyThread2(service);//写
		//MyThread2 t2=new MyThread2(service);//写
		
		//读写互斥
		//运行两个使用同一个Service对象实例的线程a,b，线程a执行上面的read方法，线程b执行上面的write方法。你会发现同一时间只允许一个线程
		//执行lock()方法后面的代码。
		MyThread1 t1=new MyThread1(service);//读
		MyThread2 t2=new MyThread2(service);//写
		
		t1.start();
		t2.start();
    }
    static public class Service {
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public void read() {
            lock.readLock().lock();
            try {
                System.out.println("获得读锁" + Thread.currentThread().getName()+ " " + System.currentTimeMillis());
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }

        public void write() {
            lock.writeLock().lock();
            try {
                System.out.println("获得写锁" + Thread.currentThread().getName()+ " " + System.currentTimeMillis());
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    static public class MyThread1 extends Thread {
        private Service service;

        public MyThread1(Service service) {
            this.service = service;
        }

        @Override
        public void run() {
            service.read();
        }
    }

    //只要出现写操作的过程就是互斥的
    static public class MyThread2 extends Thread {
        private Service service;

        public MyThread2(Service service) {
            this.service = service;
        }

        @Override
        public void run() {
            service.write();
        }
    }
}*/
//两个线程可以同时或者说是几乎同时运行lock()方法后面的代码，输出的两句话显示的时间一样，这样提高了程序的运行效率。

//4）写写互斥

//public static void main(String[] args) {
//    Service service = new Service();
//
//    MyThread2 a1 = new MyThread2(service);
//    MyThread2 a2 = new MyThread2(service);
//
//    a1.start();
//    a2.start();
//}

//5）读写互斥

//public static void main(String[] args) {
//    Service service = new Service();
//
//    MyThread1 a1 = new MyThread1(service);
//    MyThread2 a2 = new MyThread2(service);
//
//    a1.start();
//    a2.start();
//}
//运行两个使用同一个Service对象实例的线程a,b，线程a执行上面的read方法，线程b执行上面的write方法。你会发现同一时间只允许一个线程
//执行lock()方法后面的代码。

//锁的相关概念简介